/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2019年11月5日
 * V4.0
 */
package com.jphenix.service.db.mem;

import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.servlet.common.BaseParent;
import com.jphenix.share.lang.SListMap;
import com.jphenix.standard.db.IDBQuery;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Running;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 临时表（内存数据库）操作服务
 * 
 * 在首次操作内存数据库时才启动该服务
 * 启动该服务时，会先删除上次停止前创建的全部临时表，如果存在的话
 * 
 * 2019-11-05 创建
 * 2019-11-06 修改了发现的错误，和一些没想到的地方
 * 2019-11-08 去掉了删除内存数据库中上一次残留的表方法。无用
 * 2019-11-18 创建指定临时表插入数据
 * 
 * @author MBG
 * 2019年11月5日
 */
@BeanInfo({"memorydbservice"})
@Running({"1"})
@ClassInfo({"2019-11-18 16:35","临时表（内存数据库）操作服务"})
public class MemoryDbService extends ABase {

	private BaseParent  bp   = null;  //服务基类
	private IDBQuery    dbq = null;  //数据库操作类
	private CheckThread ct  = null;  //未使用临时表检测线程
	
	//临时表操作时间对照容器
	private SListMap<Long>       tbTimeOutMap   = new SListMap<Long>();
	//临时表超时时间对照容器（秒）
	private Map<String,Long>     tbOutTimeMap   = new HashMap<String,Long>();
	//默认超时时间
	private Long                 defOutTime     = 60L;
	
	/**
	 * 构造函数
	 * @author MBG
	 */
	public MemoryDbService(BaseParent base,IDBQuery dbq) throws Exception {
		super();
		if(dbq==null) {
			throw new Exception("没有获取到内存数据库操作类");
		}
		setBase(base);
		this.dbq       = dbq;
		this.bp        = base;
		//默认内存数据库中的表超时时间 (单位：秒)
		long value = lon(p("db_memory_table_timeout"));
		if(value>0) {
			defOutTime = value;
		}
		ct = new CheckThread();
		ct.start();
	}
	
	/**
	 * 检测临时表长时间未用的线程
	 * @author MBG
	 * 2019年11月5日
	 */
	private class CheckThread extends Thread {
		
		/**
		 * 构造函数
		 * @author MBG
		 */
		public CheckThread() {
			super("MemoryDbService-CheckThread");
		}
		
		/**
		 * 刷新表超时时间
		 * @param tbName 表名
		 * 2019年11月5日
		 * @author MBG
		 */
		public void refreshTb(String tbName) {
			tbTimeOutMap.put(tbName,System.currentTimeMillis());
			tbOutTimeMap.put(tbName,defOutTime);
		}
		
		/**
		 * 刷新表超时时间
		 * @param tbName   表名
		 * @param outTime  超时时间（单位：秒）
		 * 2019年11月6日
		 * @author MBG
		 */
		public void refreshTb(String tbName,Long outTime) {
			if(outTime==null || outTime<1) {
				outTime = defOutTime;
			}
			tbTimeOutMap.put(tbName,System.currentTimeMillis());
			tbOutTimeMap.put(tbName,outTime);
		}
		
		/**
		 * 移除超时时间信息
		 * @param tbName 表名
		 * 2019年11月5日
		 * @author MBG
		 */
		public void removeTb(String tbName) {
			tbTimeOutMap.remove(tbName);
			tbOutTimeMap.remove(tbName);
		}
		
		/**
		 * 覆盖方法
		 */
		@Override
        public void run() {
			while(true) {
				try {
					sleep(10000);
				}catch(Exception e) {
					e.printStackTrace();
					return;
				}
				//获取当前所有临时表的表名序列 需要用addAll设置序列元素，否则会报错
				List<String> keyList = new ArrayList<String>();
				keyList.addAll(tbTimeOutMap.keys());
				Long lastTime; //最后操作时间
				Long outTime;  //超时时间
				//当前时间
				long now = System.currentTimeMillis();
				for(String tbName:keyList) {
					lastTime = tbTimeOutMap.get(tbName);
					outTime  = tbOutTimeMap.get(tbName);
					if(outTime==null) {
						outTime = defOutTime;
					}
					if(outTime==-1) {
						//永久临时表
						continue;
					}
					if(lastTime==null || lastTime+(outTime*1000)<now) {
						info("--------CloseMemoryDB:["+tbName+"] lastTime:["+lastTime+"] outTime:["+outTime+"] now:["+now+"]");
						close(tbName);
						tbTimeOutMap.remove(tbName);
						tbOutTimeMap.remove(tbName);
					}
				}
			}
		}
	}
	
	
	/**
	 * 是否存在临时表
	 * @param tbName 表名
	 * @return 是否存在临时表
	 * 2019年11月5日
	 * @author MBG
	 */
	public boolean exists(String tbName) {
		if(tbName==null || tbName.length()<1) {
			return false;
		}
		return tbTimeOutMap.containsKey(tbName);
	}

	
	/**
	 * 创建指定临时表插入数据
	 * @param tableName    临时表的表名
	 * @param fieldNames   临时表中字段名序列
	 * @param rs           需要放入的记录集
	 * @param outTime      临时表自动删除超时时间（单位：秒）  为空时，设置默认超时时间。为-1时，当前进程永久临时表
	 * @param isAppend     是否增量插入数据（为false时，自动清空之前的表数据，再插入新的数据）
	 * @throws Exception   伊克塞普森
	 * 2019年11月18日
	 * @author MBG
	 */
	public void put(
		 	 String                   tableName
			,String[]                 fieldNames
			,List<Map<String,String>> rs
			,Long                     outTime
			,boolean                  isAppend) throws Exception {
		if(tableName==null || tableName.length()<1) {
			return;
		}
		long              beforeTime = System.currentTimeMillis(); //记录执行开始时间
		long              sqlCount   = 0;                          //处理语句数
		Connection        conn       = null;                       //数据库连接对象
		PreparedStatement stmt       = null;                       //数据库事务对象
		StringBuffer      sql        = null;                       //拼装的操作语句
		if(!tbTimeOutMap.containsKey(tableName)) {
			if(fieldNames==null || fieldNames.length<1) {
				return;
			}
			//创建内存临时表
			try {
				conn = dbq.getConn();
				//拼装语句
				sql = new StringBuffer("CREATE MEMORY Table ");
				sql.append(tableName).append("(");
				for(int i=0;i<fieldNames.length;i++) {
					if(i>0) {
						sql.append(",");
					}
					sql.append(fieldNames[i]).append(" VARCHAR");
				}
				sql.append(")");
				stmt = conn.prepareStatement(sql.toString());
			    stmt.executeUpdate();
			}catch(Exception e) {
				e.printStackTrace();
				try {
					stmt.close();
				}catch(Exception e2) {}
				try {
					conn.close();
				}catch(Exception e2) {}
				return;
			}
		}else if(!isAppend) {
			//清空表数据
			try {
				conn = dbq.getConn();
			    stmt = conn.prepareStatement("delete from "+tableName);
			    stmt.executeUpdate();
			}catch(Exception e) {
				e.printStackTrace();
				try {
					stmt.close();
				}catch(Exception e2) {}
				try {
					conn.close();
				}catch(Exception e2) {}
				return;
			}
		}
		ct.refreshTb(tableName,outTime);  //刷新表超时时间
		try {
			if(conn==null) {
				conn = dbq.getConn();
			}
		    if(rs!=null && rs.size()>0) {
				String[] params;
				for(Map<String,String> ele:rs) {
					sql    = new StringBuffer();
					params = new String[fieldNames.length];
					sql.append("insert into ").append(tableName).append(" values(");
					for(int i=0;i<fieldNames.length;i++) {
						if(i>0) {
							sql.append(",");
						}
						sql.append("?");
						params[i] = str(ele.get(fieldNames[i]));
					}
					//执行插入
				    stmt = conn.prepareStatement(sql.append(")").toString());
				    dbq.fixPreparedStatement(stmt,params);
				    stmt.executeUpdate();
				    sqlCount++;
				}
			}
		    log.sqlLog(" DataSource:[+++MEMORY DB+++] Memory Table:["+tableName+"] RowCount[" 
		    	        + sqlCount + "] Use Time ["+(System.currentTimeMillis()-beforeTime)+"]ms");
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			try {
				stmt.close();
			}catch(Exception e) {}
			try {
				conn.close();
			}catch(Exception e) {}
		}
	}
	
	/**
	 * 将记录集放入内存数据库
	 * @param fieldNames 字段名数组
	 * @param rs         记录集
	 * @param outTime    临时表未访问超时时间  如果值为-1时，为永久临时表（当前进程内保持永久）
	 * @return           记录集主键（临时表名）
	 * 2019年11月5日
	 * @author MBG
	 */
	public String put(
			String[]                  fieldNames
			,List<Map<String,String>> rs
			,Long                     outTime) throws Exception {
		if(fieldNames==null || fieldNames.length<1) {
			return null;
		}
		String tbName = bp.nid(); //构建表明
		
		ct.refreshTb(tbName,outTime);  //刷新表超时时间
		
		long beforeTime = System.currentTimeMillis(); //记录执行开始时间
		long sqlCount   = 0;                          //处理语句数
		Connection        conn = null; //数据库连接对象
		PreparedStatement stmt = null; //数据库事务对象
		try {
			conn = dbq.getConn();
			
			//拼装语句
			StringBuffer sql = new StringBuffer("CREATE MEMORY Table ");
			sql.append(tbName).append("(");
			for(int i=0;i<fieldNames.length;i++) {
				if(i>0) {
					sql.append(",");
				}
				sql.append(fieldNames[i]).append(" VARCHAR");
			}
			sql.append(")");
			
			//执行创建表
		    stmt = conn.prepareStatement(sql.toString());
		    stmt.executeUpdate();
		    
		    if(rs!=null && rs.size()>0) {
				String[] params;
				for(Map<String,String> ele:rs) {
					sql    = new StringBuffer();
					params = new String[fieldNames.length];
					sql.append("insert into ").append(tbName).append(" values(");
					for(int i=0;i<fieldNames.length;i++) {
						if(i>0) {
							sql.append(",");
						}
						sql.append("?");
						params[i] = str(ele.get(fieldNames[i]));
					}
					//执行插入
				    stmt = conn.prepareStatement(sql.append(")").toString());
				    dbq.fixPreparedStatement(stmt,params);
				    stmt.executeUpdate();
				    sqlCount++;
				}
			}
		    
		    log.sqlLog(" DataSource:[+++MEMORY DB+++] Create Table:["+tbName+"] RowCount[" 
		    	        + sqlCount + "] Use Time ["+(System.currentTimeMillis()-beforeTime)+"]ms");
		      
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			try {
				stmt.close();
			}catch(Exception e) {}
			try {
				conn.close();
			}catch(Exception e) {}
		}
		return tbName;
	}
	
	
	
	/**
	 * 创建索引
	 * @param tbName     指定表名
	 * @param fieldName  指定索引字段名
	 * @throws Exception 异常
	 * 2019年11月5日
	 * @author MBG
	 */
	public void createIndex(String tbName,String[] fieldName) throws Exception {
		if(fieldName==null || fieldName.length<1) {
			return;
		}
		
		ct.refreshTb(tbName); //刷新表超时时间
		
		Connection        conn = null; //数据库连接对象
		PreparedStatement stmt = null; //数据库事务对象
		try {
			//获取数据库连接
			conn = dbq.getConn();
			
			//拼装语句
			StringBuffer sql = new StringBuffer("create index if not exists ");
			sql.append(tbName).append("_").append(bp.nid()).append(" on ").append(tbName).append("(");
			for(int i=0;i<fieldName.length;i++) {
				if(i>0) {
					sql.append(",");
				}
				sql.append(fieldName[i]);
			}
			sql.append(")");
			dbq.e(sql.toString());
			
			//执行创建
		    stmt = conn.prepareStatement(sql.toString());
		    stmt.executeUpdate();
		    
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			try {
				stmt.close();
			}catch(Exception e) {}
			try {
				conn.close();
			}catch(Exception e) {}
		}
	}
	
	/**
	 * 将记录集放入内存数据库
	 * @param fieldNames 字段名数组
	 * @param rs         记录集
	 * @param outTime    临时表未访问超时时间
	 * @return           记录集主键（临时表名）
	 * 2019年11月5日
	 * @author MBG
	 */
	public String put(
			 String[]                 fieldNames
			,List<Map<String,String>> rs
			,String[]                 indexFieldNames
			,Long                     outTime) throws Exception {
		String tbName = put(fieldNames,rs,outTime);  //插入数据
		createIndex(tbName,indexFieldNames); //创建索引
		return tbName;
	}
	
	
	/**
	 * 获取数据库操作类
	 * @param tbName 表名，用于刷新临时表超时时间
	 * @return 数据库操作类
	 * 2019年11月5日
	 * @author MBG
	 */
	public IDBQuery db(String tbName) {
		ct.refreshTb(tbName); //刷新表超时时间
		return dbq;
	}
	
	/**
	 * 获取数据库操作类
	 * @return 数据库操作类
	 * 2019年11月6日
	 * @author MBG
	 */
	public IDBQuery db() {
		return dbq;
	}
	
	/**
	 * 删除临时表
	 * @param tableName 表名
	 * 2019年11月5日
	 * @author MBG
	 */
	public void close(String tableName) {
		ct.removeTb(tableName);
		Connection        conn = null; //数据库连接对象
		PreparedStatement stmt = null; //数据库事务对象
		try {
			//获取数据库连接
			conn = dbq.getConn();
			
			//执行删表
		    stmt = conn.prepareStatement("drop table "+tableName);
		    stmt.executeUpdate();
		    
		}catch(Exception e) {}
	}
}
